package com.hydra.base.aop;

import com.hydra.base.annotations.ControllerLog;
import com.hydra.base.utils.IpUtil;
import com.hydra.base.utils.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 打印输入输出
 *
 * @author predecessor
 */
@Configuration
@Aspect
@Slf4j
public class ControllerLogAspect {

    @Around("execution(public * com.hydra..*Controller.*(..)) && !@annotation(com.hydra.base.annotations.ControllerLogDisabled)")
    public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Method method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
        ControllerLog controllerLog = method.getAnnotation(ControllerLog.class);
        boolean enableLog = true;
        if(controllerLog != null){
            enableLog = controllerLog.enable();
        }

        long start = System.nanoTime();
        // 方法名
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        assert sra != null;
        HttpServletRequest request = sra.getRequest();
        String url = request.getRequestURL().toString();

        String ip = IpUtil.getIp(request);

        //获取方法的入参数组作为字符串，用于日志打印
        String argsMessage = this.getArgsMessage(proceedingJoinPoint);

        //打印请求日志，args为空的情况
        Object result = null;
        Throwable ex = null;
        try {
            //执行方法
            result = proceedingJoinPoint.proceed();
        }catch (Throwable e){
            ex = e;
            throw e;
        } finally {
            String resultMessage;
            long spendTime = (System.nanoTime() - start) / 1000000;
            if(ex != null){
                resultMessage = ex.getMessage();
            }else{
                resultMessage = JsonUtil.toJsonString(result);
            }

            if(enableLog){
                log.info("请求url: {}, 用户IP: {}, 请求参数: {}, 响应:{}, 耗时:{}ms", url, ip, argsMessage, resultMessage, spendTime);
            }else{
                log.info("请求url: {}, 用户IP: {}, 耗时:{}ms；已关闭参数日志", url, ip, spendTime);
            }
        }
        return result;
    }

    /**
     * 获取方法的入参数组作为字符串，用于日志打印
     * 一个方法多个参数，只打印标记了RequestBody、RequestParam、PathVariable等注解的参数，不符合条件打印null作为占位
     */
    private String getArgsMessage(ProceedingJoinPoint proceedingJoinPoint){
        Method method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
        //打印接口入参
        Object[] args = proceedingJoinPoint.getArgs();
        Object[] result = new Object[args.length];
        //要打印的请求参数
        String argsMessage = "无";
        try {
            if(args.length > 0){
                //获取方法每个参数的注解二维数组
                Annotation[][] parameterAnnotations = method.getParameterAnnotations();

                //只打印标记了RequestBody、RequestParam、PathVariable等注解的参数的日志
                List<Class<? extends Annotation>> requestAnnotations = Arrays.asList(
                        RequestBody.class,
                        PathVariable.class,
                        RequestParam.class
                );
                //遍历方法的所有入参，判断是否带了需要打印日志的参数注解
                for (int i = 0; i < args.length; i++) {
                    result[i] = args[i];
                    if(args[i] != null){
                        //注解的数组长度跟入参是一样的，没有注解就是一个空的数组
                        Annotation[] annotationArr = parameterAnnotations[i];
                        List<Annotation> collect = Arrays.stream(annotationArr).filter(item -> requestAnnotations.contains(item.annotationType())).collect(Collectors.toList());
                        if(collect.isEmpty()){
                            result[i] = null;//没有标记注解的，直接将入参设置为null，避免被打印出来
                        }
                    }
                }
                argsMessage = JsonUtil.toJsonString(result);
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return argsMessage;
    }

}
